/**
 * 插件公共函数库
 */
;(function(g){

  if(g.wxqf_common) return;

  var common = g.wxqf_common = { };

  /**
   * 属性区
   * @type {Boolean}
   */

  // 是否开启DEBUG
  common.DEBUG = true;

  // 页面地址
  // {token} 所需要的token，
  // ...
  common._pageUrls = {
    login          : 'https://mp.weixin.qq.com/#wxqfzs_login',
    massSend       : 'https://mp.weixin.qq.com/cgi-bin/masssendpage?t=mass/send&token={token}&lang=zh_CN',
    home           : 'https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token={token}',
    media          : 'https://mp.weixin.qq.com/cgi-bin/appmsg?begin=0&count=10&t=media/appmsg_list&type=10&action=list&token={token}&lang=zh_CN',
    user           : 'https://mp.weixin.qq.com/cgi-bin/contactmanage?t=user/index&pagesize=10&pageidx=0&type=0&groupid=0&token={token}&lang=zh_CN',
    setting        : 'https://mp.weixin.qq.com/cgi-bin/settingpage?t=setting/index&action=index&token={token}&lang=zh_CN',
    userinfo       : 'https://mp.weixin.qq.com/cgi-bin/getcontactinfo',
    singlesend     : 'https://mp.weixin.qq.com/cgi-bin/singlesend',
    singlesendpage : 'https://mp.weixin.qq.com/cgi-bin/singlesendpage?t=message/send&action=index&tofakeid=2681469461&token={token}&lang=zh_CN'
  };



  /**
   * 获取一个未替换的页面地址
   */
  common.getPageUrl = function(pageName, token)
  {

    var url = this._pageUrls[pageName];
    if (!url) return '';
    token = token || '';
    return url.replace('{token}', token);
  };


  /////////////////////////////////////////////////////////////////////
  ///
  ///


  /**
   * 获取当前日期并返回 Y-m-d H:i:s格式
   * @return {String}
   */
  common.getDate = function()
  {
    var tmpD = new Date;
    var year, month, day, hour, minute, second;

    year = tmpD.getFullYear();

    month = (tmpD.getMonth() + 1) + '';
    if (month.length < 2) month = '0' + month;

    date = tmpD.getDate() + '';
    if (date.length < 2) date = '0' + date;

    hour = tmpD.getHours() + '';
    if(hour.length < 2) hour = '0' + hour;

    minute = tmpD.getMinutes() + '';
    if (minute.length < 2) minute = '0' + minute;

    second = tmpD.getSeconds() + '';
    if (second.length < 2) second = '0' + second;

    return year + '-' + month + '-' + date + ' ' + hour + ':' + minute + ':' + second;
  };


  /**
   * 将图片转换为字符串
   * @param {String} url 图片地址
   * @param {Function} cb 回调函数
   * @return
   */
  common.imageToData = function(url, cb)
  {
    this.loadImage(url, function(err, img){
      if ( err ) {
        cb(err, null);
        return;
      }
      var canvas = document.createElement('canvas');
      canvas.width  = img.width;
      canvas.height = img.height;
      var cxt = canvas.getContext('2d');
      cxt.drawImage(img, 0, 0);

      cb(err, canvas.toDataURL());
    });
  };

  /**
   * 获取一个url的图片的dom对象
   * @param {String} url 图片地址
   * @param {Function} cb 回调函数
   */
  common.loadImage = function(url, cb)
  {
    //var img = new Image();
    var img = document.createElement('img');
    img.onload = function(){
      // 防止gif图多次onload
      img.onload = null;
      if ( cb ) cb(null, img);
    }
    img.onerror = function() {
      if ( cb ) cb('loadimage-error:'+url, null);
    }
    img.src = url;
  };

  /**
   * 获取这个页面的script的内容
   */
  common.getScriptCode = function()
  {
    if (this.scriptCode) {
      return this.scriptCode;
    }
    var me = this;
    $.each($('script'), function(){
      // js文件则跳过
      if ($(this).attr('src')) return true;
      me.scriptCode += "\n======script--boundary======\n" + $(this).html();
    });
    return me.scriptCode;
  };


  /**
   * 转换url参数为array
   */
  common.parseUrl = function(str, component)
  {
    // http://kevin.vanzonneveld.net
    // +      original by: Steven Levithan (http://blog.stevenlevithan.com)
    // + reimplemented by: Brett Zamir (http://brett-zamir.me)
    // + input by: Lorenzo Pisani
    // + input by: Tony
    // + improved by: Brett Zamir (http://brett-zamir.me)
    // %          note: Based on http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
    // %          note: blog post at http://blog.stevenlevithan.com/archives/parseuri
    // %          note: demo at http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
    // %          note: Does not replace invalid characters with '_' as in PHP, nor does it return false with
    // %          note: a seriously malformed URL.
    // %          note: Besides function name, is essentially the same as parseUri as well as our allowing
    // %          note: an extra slash after the scheme/protocol (to allow file:/// as in PHP)
    // *     example 1: parse_url('http://username:password@hostname/path?arg=value#anchor');
    // *     returns 1: {scheme: 'http', host: 'hostname', user: 'username', pass: 'password', path: '/path', query: 'arg=value', fragment: 'anchor'}
    var query, key = ['source', 'scheme', 'authority', 'userInfo', 'user', 'pass', 'host', 'port',
              'relative', 'path', 'directory', 'file', 'query', 'fragment'],
      ini = (this.php_js && this.php_js.ini) || {},
      mode = (ini['phpjs.parse_url.mode'] &&
        ini['phpjs.parse_url.mode'].local_value) || 'php',
      parser = {
        php: /^(?:([^:\/?#]+):)?(?:\/\/()(?:(?:()(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?()(?:(()(?:(?:[^?#\/]*\/)*)()(?:[^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
        strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
        loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // Added one optional slash to post-scheme to catch file:/// (should restrict this)
      };

    var m = parser[mode].exec(str),
      uri = {},
      i = 14;
    while (i--) {
      if (m[i]) {
        uri[key[i]] = m[i];
      }
    }

    if (component) {
      return uri[component.replace('PHP_URL_', '').toLowerCase()];
    }
    if (mode !== 'php') {
      var name = (ini['phpjs.parse_url.queryKey'] &&
          ini['phpjs.parse_url.queryKey'].local_value) || 'queryKey';
      parser = /(?:^|&)([^&=]*)=?([^&]*)/g;
      uri[name] = {};
      query = uri[key[12]] || '';
      query.replace(parser, function ($0, $1, $2) {
        if ($1) {uri[name][$1] = $2;}
      });
    }
    delete uri.source;
    uri.queryParams = {};
    if ( uri.query ) {
      this.parseStr(uri.query, uri.queryParams);
    }
    return uri;
  };

  /**
   * 转换字符串为一个数组
   */
  common.parseStr = function(str, array)
  {
    // http://kevin.vanzonneveld.net
    // +   original by: Cagri Ekin
    // +   improved by: Michael White (http://getsprink.com)
    // +    tweaked by: Jack
    // +   bugfixed by: Onno Marsman
    // +   reimplemented by: stag019
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: stag019
    // +   input by: Dreamer
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/)
    // +   input by: Zaide (http://zaidesthings.com/)
    // +   input by: David Pesta (http://davidpesta.com/)
    // +   input by: jeicquest
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // %        note 1: When no argument is specified, will put variables in global scope.
    // %        note 1: When a particular argument has been passed, and the returned value is different parse_str of PHP. For example, a=b=c&d====c
    // *     example 1: var arr = {};
    // *     example 1: parse_str('first=foo&second=bar', arr);
    // *     results 1: arr == { first: 'foo', second: 'bar' }
    // *     example 2: var arr = {};
    // *     example 2: parse_str('str_a=Jack+and+Jill+didn%27t+see+the+well.', arr);
    // *     results 2: arr == { str_a: "Jack and Jill didn't see the well." }
    // *     example 3: var abc = {3:'a'};
    // *     example 3: parse_str('abc[a][b]["c"]=def&abc[q]=t+5');
    // *     results 3: JSON.stringify(abc) === '{"3":"a","a":{"b":{"c":"def"}},"q":"t 5"}';


    var strArr = String(str).replace(/^&/, '').replace(/&$/, '').split('&'),
      sal = strArr.length,
      i, j, ct, p, lastObj, obj, lastIter, undef, chr, tmp, key, value,
      postLeftBracketPos, keys, keysLen,
      fixStr = function (str) {
        return decodeURIComponent(str.replace(/\+/g, '%20'));
      };

    if (!array) {
      array = this.window;
    }

    for (i = 0; i < sal; i++) {
      tmp = strArr[i].split('=');
      key = fixStr(tmp[0]);
      value = (tmp.length < 2) ? '' : fixStr(tmp[1]);

      while (key.charAt(0) === ' ') {
        key = key.slice(1);
      }
      if (key.indexOf('\x00') > -1) {
        key = key.slice(0, key.indexOf('\x00'));
      }
      if (key && key.charAt(0) !== '[') {
        keys = [];
        postLeftBracketPos = 0;
        for (j = 0; j < key.length; j++) {
          if (key.charAt(j) === '[' && !postLeftBracketPos) {
            postLeftBracketPos = j + 1;
          }
          else if (key.charAt(j) === ']') {
            if (postLeftBracketPos) {
              if (!keys.length) {
                keys.push(key.slice(0, postLeftBracketPos - 1));
              }
              keys.push(key.substr(postLeftBracketPos, j - postLeftBracketPos));
              postLeftBracketPos = 0;
              if (key.charAt(j + 1) !== '[') {
                break;
              }
            }
          }
        }
        if (!keys.length) {
          keys = [key];
        }
        for (j = 0; j < keys[0].length; j++) {
          chr = keys[0].charAt(j);
          if (chr === ' ' || chr === '.' || chr === '[') {
            keys[0] = keys[0].substr(0, j) + '_' + keys[0].substr(j + 1);
          }
          if (chr === '[') {
            break;
          }
        }

        obj = array;
        for (j = 0, keysLen = keys.length; j < keysLen; j++) {
          key = keys[j].replace(/^['"]/, '').replace(/['"]$/, '');
          lastIter = j !== keys.length - 1;
          lastObj = obj;
          if ((key !== '' && key !== ' ') || j === 0) {
            if (obj[key] === undef) {
              obj[key] = {};
            }
            obj = obj[key];
          }
          else { // To insert new dimension
            ct = -1;
            for (p in obj) {
              if (obj.hasOwnProperty(p)) {
                if (+p > ct && p.match(/^\d+$/g)) {
                  ct = +p;
                }
              }
            }
            key = ct + 1;
          }
        }
        lastObj[key] = value;
      }
    }
  };

  /**
   * 随机一个数字
   * @param  {[type]} min [description]
   * @param  {[type]} max [description]
   * @return {[type]}     [description]
   */
  common.rand = function(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  };

  /**
   * rc4算法
   * @param {String} key 密钥
   * @param {Stirng} text 密文/明文
   */
  common.rc4 = function(key, text) {
    var s = new Array();
    for (var i=0; i<256; i++) {
      s[i] = i;
    }
    var j = 0, x;
    for (i=0; i<256; i++) {
      j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
      x = s[i];
      s[i] = s[j];
      s[j] = x;
    }
    i = j = 0;
    var ct = [];
    for (var y=0; y<text.length; y++) {
      i = (i + 1) % 256;
      j = (j + s[i]) % 256;
      x = s[i];
      s[i] = s[j];
      s[j] = x;
      ct.push(String.fromCharCode(text.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]));
    }
    return ct.join('');
  };

  /**
   * 什么也不做的函数
   */
  common.loop = function(){ };

})(window);





/**
 * AnTask
 * v0.0.1
 * $Id: AnTask.js 1025 2013-11-08 20:01:12Z liw $
 */
;(function(g){

  if (g.AnTask) return false;

  var _errno = {

    1: 'no task to do',
    2: 'get taskDetail failed',
    3: 'complete failed: taskid not in the runningList',

    10001: 'params type mismatched'
  };


  var Task = g.AnTask = function(config)
  {
    config = config || {};

    this._pk = config.pk || 'id'; // task中主键，用于区分每个任务
    this._id = config.id || this.error('AnTask init failed: taskId is empty'); // 用户一类任务唯一属性

    // 内存控制选项
    this._maxCompleteCount = config.maxCompleteCount || null; // 最大完成数量记录数，默认没限制
    this._maxFailCount     = config.maxFailCount     || null; // 最大失败数量记录数，默认没限制

    this._maxRunTimes      = config.maxRunTimes      || null; // 单个任务最大执行次数
    this._maxWaitingTime   = config.maxWaitingTime   || null; // 单个任务最大等待时间（秒）

    // 队列选项
    this._toDoList     = []; // 待做的任务主键队列
    this._completeList = []; // 已完成的任务主键队列
    this._failList     = []; // 失败的任务主键队列
    this._runningList  = []; // 正在执行的任务

    this._detailSet      = {}; // 所有任务详情
    this._runningTimeSet = {}; // 开始执行的时间

    // 任务扩展属性
    this._ext = null;

    this._completeNum = 0;
    this._failNum     = 0;

    // 主键类型（number/string）
    this._pkType = null;

    this.init();
  };


  /**
   * 原型对象
   * @type {Object}
   */
  var pro = Task.prototype;

  /**
   * 初始化
   */
  pro.init = function()
  {
    var me = this;
    this._timer = setInterval(function(){
      me.checkRunning();
      me.gc();
    }, this._maxWaitingTime && this._maxWaitingTime*4*1000 || 8*3600*1000);
  };


  /**
   * 添加任务
   * @param {Array} tasks 任务列表
   * @param {Function} cb callback
   */
  pro.add = function(tasks, cb)
  {
    cb = cb || this.loop;

    if (!(tasks instanceof Array)) {
      cb({'result': false, 'error_msg':_errno[10001], 'error_no':10001});
      return;
    }
    var successNum = 0, failNum = 0;

    if(this._pkType === null) {
      this._pkType = isNaN(tasks[0][this._pk]) ? 'string' : 'number';
    }

    for (var i = 0; i < tasks.length; i++) {
      var id = tasks[i][this._pk];
      if (
          id &&
          !_inArray(id, this._toDoList) &&
          !_inArray(id, this._completeList) &&
          !_inArray(id, this._failList) &&
          !_inArray(id, this._runningList)
        ) {
        id = this.bulidId(id);
        // 设置执行次数
        if(!tasks[i].__runTimes) tasks[i].__runTimes = 0;
        if(!tasks[i].__status) tasks[i].__status = 'todo';
        this._toDoList.push(id);
        this._detailSet[id] = tasks[i];
        successNum ++;
        continue;
      }
      failNum ++;
    }

    cb({'result': true, 'successNum': successNum, 'failNum': failNum, 'overview': this.overview()});
  };

  /**
   * 获取一个任务来执行
   * @param {Function} cb callback
   */
  pro.getOne = function(cb)
  {
    cb = cb || this.loop;

    var id = this._toDoList.shift();

    if(!id) {
      cb({'result': false, 'error_msg': _errno[1], 'error_no': 1});
      return;
    }

    var detail = this._detailSet[id];
    if (!detail) {
      cb({'result': false, 'error_msg': _errno[2], 'error_no': 2});
      return;
    }


    this._runningList.push(id); // 添加到待执行
    this._runningTimeSet[id] = (new Date).getTime(); // 记录开始时间
    this._detailSet[id].__runTimes ++; // 执行次数+1
    this._detailSet[id].__status = 'running';

    cb({'result': true, 'task':detail, 'overview': this.overview()});
  };

  /**
   * 完成一个任务
   * @param {Object} msg 任务完成的详情
   *                     msg.result {boolean} true/false
   *                     msg.error_msg {String}
   *                     msg.id {int|string} 任务id
   * @param {Function} cb callback
   */
  pro.completeOne = function(msg, cb)
  {
    cb = cb || this.loop;
    if(!msg || !msg.id) {
      cb({'result': false, 'error_msg':_errno[10001], 'error_no':10001});
      return;
    }

    var id  = this.bulidId(msg.id);
    var idx = _indexOf(id, this._runningList);

    if (idx == -1) {
      cb({'result': false, 'error_msg': _errno[3], 'error_no': 3});
      return;
    }

    // 从runningList删除
    this._runningList.splice(idx, 1);
    delete this._runningTimeSet[id];

    var detail = this._detailSet[id];
    if (!detail) {
      cb({'result': false, 'error_msg': _errno[2], 'error_no': 2});
      return;
    }

    var status = '';

    // 没成功 判断执行次数
    if (!msg.result && (this._maxRunTimes === null || detail.__runTimes < this._maxRunTimes)) {
      // 重新添加到任务
      this.add([detail]);
      status = 'retry';
    } else if (!msg.result) {
      this._failList.push(id);
      this._detailSet[id].error_msg = msg.error_msg || '';
      this._failNum++;
      status = 'failed';
    } else {
      this._completeList.push(id);
      this._completeNum ++;
      status = 'complete';
    }

    this._detailSet[id].__status = status;

    cb({'result': true, 'status': status, 'overview': this.overview()});
  };

  /**
   * 查询一个任务
   * @param {Mixed}
   */
  pro.info = function(id, cb){
    cb = cb || this.loop;
    if (!id) {
      cb({'result': false, 'error_msg':_errno[10001], 'error_no': 10001});
      return false;
    }
    if (!this._detailSet[id]) {
      cb({'result': false, 'error_msg': _errno[2], 'error_no': 2});
      return false;
    }
    cb({'result': true, 'task': this._detailSet[id], 'overview': this.overview()});
  };

  /**
   * 执行超过最大等待时间的处理
   */
  pro.checkRunning = function()
  {
    if (!this._maxWaitingTime) return false;

    var id , startT, maxT = this._maxWaitingTime*1000, me = this;

    for (var i = 0; i < this._runningList.length; i++) {

      (function(id){
        setTimeout(function(){
          var nowT = (new Date).getTime();
          startT = me._runningTimeSet[id];
          if (!startT || (nowT - startT > maxT)) {
            me.completeOne({'result':false, 'error_msg':'checkRunning_timeout', id:id});
          }
        }, 0);
      })(this._runningList[i]);

    }

  };

  /**
   * 清除无用的数据
   */
  pro.gc = function()
  {
    var me = this;
    if (parseInt(Math.random()*100) > 30) return false;
    setTimeout(function(){
      var removeIds = [], diffNum;
      // 已完成数量
      if (me._maxCompleteCount && me._completeList.length > me._maxCompleteCount) {
        diffNum = me._completeList.length - me._maxCompleteCount;
        removeIds = removeIds.concat(me._completeList.slice(0, diffNum));
        me._completeList = me._completeList.slice(diffNum -1);
      }
      // 已失败的数量
      if (me._maxFailCount && me._failList.length > me._maxFailCount) {
        diffNum = me._failList.length - me._maxFailCount;
        removeIds = removeIds.concat(me._failList.slice(0, diffNum));
        me._failList = me._failList.slice(diffNum -1);
      }
      // del
      for (var i = 0; i < removeIds.length; i++) {
        delete me._detailSet[removeIds[i]];
      }

    }, 500);
  };

  /**
   * 获取任务概括
   */
  pro.overview = function(cb)
  {
    var data = {
      'todoNum'          : this._toDoList.length,
      'runningNum'       : this._runningList.length,
      'failNum'          : this._failNum,
      'completeNum'      : this._completeNum,
      'pk'               : this._pk,
      'pkType'           : this._pkType,
      'id'               : this._id,
      'maxCompleteCount' : this._maxCompleteCount,
      'maxFailCount'     : this._maxFailCount,
      'maxRunTimes'      : this._maxRunTimes,
      'maxWaitingTime'   : this._maxWaitingTime,
      'ext'              : this._ext
    };

    if (cb) cb(data);
    return data;
  };

  /**
   * 设置ext
   */
  pro.setExt = function(ext, cb)
  {
    this._ext = ext;
    if (cb) cb({'result': true, 'overview': this.overview()});
    return true;
  }

  /**
   * 转换pk
   */
  pro.bulidId = function(id)
  {
    if (!id || !this._pkType) return id;
    return this._pkType == 'number' ? id -0 : id + '';
  }

  /**
   * 错误抛出函数
   * @param {String} msg 错误消息
   */
  pro.error = function(msg)
  {
    throw new Error(msg);
  };

  /**
   * 什么也不做的函数
   */
  pro.loop = function(){ };




  // 私有方法
  // ---------------------------------------------------------------


  /**
   * 检测一个值是否在数组中并返回其索引值（失败返回-1）
   * @param  {Mixed} val 值
   * @param  {Array} arr 数组
   * @return {Int}
   */
  function _indexOf(val, arr)
  {
    if (arr.indexOf) return arr.indexOf(val);

    for (var i = 0; i < arr.length; i++) {
      if (val === arr[i]) return i;
    }
    return -1;
  }

  /**
   * 值是否在一个数组中
   * @param  {Mixed} val 值
   * @param  {Array} arr 数组
   * @return {Boolean} true/false
   */
  function _inArray(val, arr)
  {
    return _indexOf(val, arr) == -1 ? false : true;
  }

})(window);